﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using TeF.Runner.Common.Extensions;

namespace TeF.Runner.Services
{
    internal class TestRunner : ITestRunner
    {
        public void RunTests(Test[] tests)
        {
            if (tests == null)
                throw new ArgumentNullException("tests");

            ResetTests(tests);

            var testsGroupedByContainersByAssemblies =
                tests
                    .GroupBy(t => t.Parent)
                    .GroupBy(g => g.Key.Parent.Parent)
                    .OrderBy(g => g.Key.Name)
                    .ToArray();

            foreach (var testsGroupedByContainers in testsGroupedByContainersByAssemblies)
            {
                var domain = AppDomain.CreateDomain("TeF_Test_Domain");

                try
                {
                    var currentAssembly = domain.Load(Assembly.GetExecutingAssembly().GetName());

                    var testRunHelperType = typeof(TestRunHelper);
                    var testRunHelper = (TestRunHelper)domain.CreateInstanceFrom(currentAssembly.Location, testRunHelperType.FullName).Unwrap();

                    var testAssemblyPath = testsGroupedByContainers.Key.Assembly.Location;
                    testRunHelper.LoadAssembly(testAssemblyPath);

                    var orderedTestsGroupedByContainers =
                        testsGroupedByContainers
                            .OrderBy(g => g.Key.Parent.Name);

                    foreach (var testsGroupedByContainer in orderedTestsGroupedByContainers)
                    {
                        var fullNameOfType = testsGroupedByContainer.Key.Type.FullName;

                        testRunHelper.CreateInstanceOfType(fullNameOfType);

                        var orderedTests =
                            testsGroupedByContainer
                                .OrderBy(i => i.Name);

                        foreach (var test in orderedTests)
                        {
                            var methodName = test.MethodInfo.Name;

                            RunTest(test, () => testRunHelper.ExecuteMethodOfType(fullNameOfType, methodName));
                        }

                        testRunHelper.DistroyInstanceOfType(fullNameOfType);
                    }
                }
                finally
                {
                    AppDomain.Unload(domain);
                }
            }
        }


        private void ResetTests(IEnumerable<Test> tests)
        {
            if (tests == null)
                throw new ArgumentNullException("tests");

            tests.DoForEach(t => t.Reset());
        }

        private static void RunTest(Test test, Action action)
        {
            if (test == null)
                throw new ArgumentNullException("test");
            if (test.State == TestState.Skipped)
                throw new ArgumentException("test");
            if (action == null)
                throw new ArgumentNullException("action");

            try
            {
                test.Reset();
                action();
                test.SetState(TestState.Passed);
            }
            catch (TestRunException ex)
            {
                test.SetState(TestState.Failed);
                test.SetLog(ex.InnerException.InnerException.ToString());
            }
        }
    }
}
